/* **********************************************************************************
*
* Copyright (c) Microsoft Corporation. All rights reserved.
*
* This source code is subject to terms and conditions of the Shared Source License
* for DSL Editor PowerToy. A copy of the license can be found in the License.htm file
* at the root of this distribution. If you can not locate the Shared Source License
* for DSL Editor PowerToy, please obtain a copy from: http://www.codeplex.com/dsltreegrideditor/Project/License.aspx.
* By using this source code in any fashion, you are agreeing to be bound by
* the terms of the Shared Source License for DSL Editor PowerToy.
*
* You must not remove this notice, or any other, from this software.
*
* **********************************************************************************/
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.Design;
using System.Text;
using System.Windows.Forms;

using Microsoft.VisualStudio;
using Microsoft.VisualStudio.Modeling.Shell;
using Microsoft.VisualStudio.Shell.Interop;

namespace DslEditorPowerToy.Controls.Languages
{
    /// <summary>
    /// A class to broker selection changes between unmanaged VS and a managed component.
    /// Propagates selection changes in VS to control
    /// Propages selection changes from controls to VS
    /// Acts as ISelectionContainer and ISelectionService on behalf of control
    /// </summary>
    internal class ComponentSelectionBroker : IDisposable, ISelectionService, ISelectionContainer
    {
        private IServiceProvider _serviceProvider = null;
        private Component _component = null;
        private ModelingDocView _docView = null;
        private ArrayList _selectedItems = null;
        private IHelpService _selectionHelpService = null;
        private object _primarySelection = null;

        #region Constructors
        public ComponentSelectionBroker(IServiceProvider serviceProvider, Component component)
        {
            this._selectedItems = new ArrayList();

            this._serviceProvider = serviceProvider;
            this._component = component;

            Initialise();
        }
        ~ComponentSelectionBroker()
        {
            this.Dispose(false);
        }
        #endregion

        #region IDisposable Members
        public void Dispose()
        {
            this.Dispose(true);
            GC.SuppressFinalize(this);
        }
        #endregion

        #region ISelectionContainer Members

        public int CountObjects(uint flags, out uint count)
        {
            count = 0;
            if ((1 == (flags & 1))
                || (2 == (flags & 2)))
                count = (uint) this._selectedItems.Count;

            return 0;
        }

        public int GetObjects(uint flags, uint count, object[] objects)
        {
            if ((1 == (flags & 1))
                || (2 == (flags & 2)))
            {
                //Return selected objects
                IList selectedItems = this._selectedItems;
                if (count <= selectedItems.Count)
                    selectedItems.CopyTo(objects, 0);
            }

            return 0;
        }

        public int SelectObjects(uint cSelect, object[] apUnkSelect, uint dwFlags)
        {
            //Do nothing here
            return 0;
        }

        #endregion

        #region ISelectionService Members

        public bool GetComponentSelected(object component)
        {
            //Check if item in cache
            for (int index = 0; index < this._selectedItems.Count; index++)
            {
                if (component == this._selectedItems[index])
                    return true;
            }

            return false;
        }

        public ICollection GetSelectedComponents()
        {
            return ArrayList.ReadOnly(this._selectedItems);
        }

        public object PrimarySelection
        {
            get
            {
                //Ensure valid
                if (null != this._primarySelection)
                    return this._primarySelection;

                //Return first item in cache
                if (this._selectedItems.Count > 0)
                    return this._selectedItems[0];

                return null;
            }
            set
            {
                this._primarySelection = value;
            }
        }

        /// <summary>
        /// Indicates a selection change in control
        /// </summary>
        public event EventHandler SelectionChanged;
        /// <summary>
        /// Indicates selection changing in control
        /// </summary>
        public event EventHandler SelectionChanging;

        public int SelectionCount
        {
            get
            {
                return this._selectedItems.Count;
            }
        }

        public void SetSelectedComponents(ICollection components, SelectionTypes selectionType)
        {
            throw new NotImplementedException();
        }

        public void SetSelectedComponents(ICollection components)
        {
            SetSelectedComponentsInternal(components);
        }

        #endregion

        #region Protected Methods
        protected virtual void Dispose(bool disposing)
        {
            try
            {
                if (disposing)
                {
                    //Unregister from control selection events
                    if (null != this._component)
                    {
                        if (this._component is ISelectionService)
                            ((ISelectionService)this._component).SelectionChanged -= Control_SelectionChanged;
                    }

                    //Unregister from diagram selection events
                    if (null != this._docView)
                        this._docView.SelectionChanged -= new EventHandler(OnDocViewSelectionChanged);

                    //Unregister from global selection events
                    if (null != this._serviceProvider)
                    {
                        IMonitorSelectionService monitorSelectionService = this._serviceProvider.GetService(typeof(IMonitorSelectionService)) as IMonitorSelectionService;
                        if (null != monitorSelectionService)
                            monitorSelectionService.SelectionChanged -= this.OnMonitorSelectionSeviceSelectionChanged;
                    }
                }
            }
            finally
            {
                //base.Dispose(disposing);
            }
        }
        /// <summary>
        /// Raises selection changed event on control
        /// </summary>
        protected void OnSelectionChanged()
        {
            if (null != this.SelectionChanged)
                this.SelectionChanged(this, new EventArgs());
        }
        /// <summary>
        /// Raises selection changing event on control
        /// </summary>
        protected void OnSelectionChanging()
        {
            if (null != this.SelectionChanging)
                this.SelectionChanging(this, new EventArgs());
        }
        /// <summary>
        /// Raises all selection changes
        /// </summary>
        private void OnSelectionChanged(ISelectionProvider selectionProvider, SelectionChangeSource source)
        {
            //Raise event to owner
            if (null != this.EnvironmentSelectionChanged)
                this.EnvironmentSelectionChanged(this, new SelectionChangedEventArgs(selectionProvider, source));

            //Propagate selection change to control
            if (null != this._component)
            {
                if (this._component is IMonitorSelection)
                    ((IMonitorSelection)this._component).NotifySelectionChanged(selectionProvider, source);
            }
        }
        #endregion

        #region Public Events
        /// <summary>
        /// Indicates a change in selection in VS environment
        /// </summary>
        public event SelectionChangedEventHandler EnvironmentSelectionChanged;
        #endregion

        #region Public Methods
        /// <summary>
        /// Registers document view for selection handling
        /// </summary>
        /// <param name="docView"></param>
        public void RegisterDocView(ModelingDocView docView)
        {
            if (null == docView)
                return;

            //Unregister events from previous instance
            if (null != this._docView)
                this._docView.SelectionChanged -= OnDocViewSelectionChanged;

            //Save instance
            this._docView = docView;

            //Register selection change events
            docView.SelectionChanged += new EventHandler(OnDocViewSelectionChanged);
        }
        #endregion

        #region Control Handlers
        /// <summary>
        /// Handles selection change from the control
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Control_SelectionChanged(object sender, EventArgs e)
        {
            if (null == this._component)
                return;

            //Propagate controls selectable items to VS
            if (this._component is ISelectionService)
            {
                ISelectionService selectionService = this._component as ISelectionService;
                
                //Cache selection in our ISelectionContainer
                this.SetSelectedComponentsInternal(selectionService.GetSelectedComponents());
            }
        }
        #endregion

        #region Private Methods
        /// <summary>
        /// Initialises broker
        /// </summary>
        private void Initialise()
        {
            //Listens for selection changes from VS
            if (null != this._serviceProvider)
            {
                IMonitorSelectionService monitorSelectionService = this._serviceProvider.GetService(typeof(IMonitorSelectionService)) as IMonitorSelectionService;
                if (null != monitorSelectionService)
                    monitorSelectionService.SelectionChanged += this.OnMonitorSelectionSeviceSelectionChanged;
            }

            //Listens for selection changes from control
            if (null != this._component)
            {
                if (this._component is ISelectionService)
                    ((ISelectionService)this._component).SelectionChanged += new EventHandler(Control_SelectionChanged);
            }
        }
        /// <summary>
        /// Handles a change in diagram selection
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void OnDocViewSelectionChanged(object sender, EventArgs e)
        {
            ISelectionService selectionService = this._docView as ISelectionService;
            if (null != selectionService)
                OnSelectionChanged(new SelectionProvider(selectionService), SelectionChangeSource.Diagram);
        }
        /// <summary>
        /// Handles a change in global VS selection
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void OnMonitorSelectionSeviceSelectionChanged(object sender, MonitorSelectionEventArgs e)
        {
            //Try for managed selection service
            ISelectionService selectionService = e.NewValue as ISelectionService;
            if (null != selectionService)
                OnSelectionChanged(new SelectionProvider(selectionService), SelectionChangeSource.Environment);
            else
            {
                //Try for unmanaged selection container
                ISelectionContainer selectionContainer = e.NewValue as ISelectionContainer;
                if (null != selectionContainer)
                    OnSelectionChanged(new SelectionProvider(selectionContainer), SelectionChangeSource.Environment);
            }
        }
        /// <summary>
        /// Propagates selected components to VS
        /// </summary>
        /// <param name="components"></param>
        private void SetSelectedComponentsInternal(ICollection selectedComponents)
        {
            //Ensure selection is different than before
            if (true == IsSameAsCache(selectedComponents))
                return;

            OnSelectionChanging();

            //Clear internal cache
            this._selectedItems.Clear();

            //Copy items to cache
            if (null != selectedComponents)
            {
                IList selectedItems = this._selectedItems;
                foreach (object component in selectedComponents)
                    selectedItems.Add(component);
            }

            //Reset primary selection
            if ((null != this._primarySelection) 
                && (false == this._selectedItems.Contains(this._primarySelection)))
                this._primarySelection = null;

            //Raise selection change to VS
            IVsTrackSelectionEx trackSelectionService = (IVsTrackSelectionEx)this._serviceProvider.GetService(typeof(IVsTrackSelectionEx));
            if (null != trackSelectionService)
                ErrorHandler.ThrowOnFailure(trackSelectionService.OnSelectChange(this));

            //Clear selection context attributes
            if (null != this._selectionHelpService)
                this._selectionHelpService.ClearContextAttributes();

            OnSelectionChanged();
        }
        /// <summary>
        /// Returns if selected components are different from those in cache
        /// </summary>
        /// <param name="selectedComponents"></param>
        /// <returns></returns>
        private bool IsSameAsCache(ICollection selectedComponents)
        {
            //Check if count is the same (easy check)
            int selectedCount = (selectedComponents != null) ? selectedComponents.Count : 0;
            bool isDiffSelection = this._selectedItems.Count != selectedCount;
            if ((false == isDiffSelection)
                && (null != selectedComponents))
            {
                //Check each item
                foreach (object component in selectedComponents)
                {
                    isDiffSelection = !this._selectedItems.Contains(component);
                    if (true == isDiffSelection)
                        break;
                }
            }

            return !isDiffSelection;
        }
        /// <summary>
        /// Returns selection help service
        /// </summary>
        /// <returns></returns>
        private IHelpService GetSelectionHelpService()
        {
            //Return singleton instance
            if (null == this._selectionHelpService)
            {
                IHelpService helpService = (IHelpService)this._serviceProvider.GetService(typeof(IHelpService));
                if (null != helpService)
                    this._selectionHelpService = helpService.CreateLocalContext(HelpContextType.Selection);
            }

            return this._selectionHelpService;
        }
        #endregion
    }

    public delegate void SelectionChangedEventHandler(object sender, SelectionChangedEventArgs e);

    public class SelectionChangedEventArgs : EventArgs
    {
        private ISelectionProvider _provider = null;
        private SelectionChangeSource _source = SelectionChangeSource.Any;

        #region Constructors
        public SelectionChangedEventArgs(ISelectionProvider provider, SelectionChangeSource source)
        {
            this._provider = provider;
            this._source = source;
        }
        #endregion

        #region Public Properties
        /// <summary>
        /// Gets the provider of the selection
        /// </summary>
        public ISelectionProvider SelectionProvider
        {
            get
            {
                return this._provider;
            }
        }
        /// <summary>
        /// Gets the selection source
        /// </summary>
        public SelectionChangeSource Source
        {
            get
            {
                return this._source;
            }
        }
        #endregion
    }
}
